<!DOCTYPE html>
<html>
<head>
    <title>Vizit</title>
    <link rel="stylesheet" href="css/style.css">
    <script src="node_modules/jquery/dist/jquery.js"></script>
    <script src="node_modules/@svgdotjs/svg.js/dist/svg.js"></script>
    <script src="node_modules/@svgdotjs/svg.draggable.js/dist/svg.draggable.js"></script>
    <script src="scripts/snap.svg.js"></script>
    <script src="http://svg.dabbles.info/animate-object-path.js"></script>
    <!--    <script src="//cdnjs.cloudflare.com/ajax/libs/velocity/2.0.6/velocity.min.js"></script>-->

</head>
<body>

<script>
    // 定义svg对象, 在body里面添加一个svg
    let svg = SVG().addTo('body').attr({width: 1000, height: 800, id: 'svg'});
    var snap = Snap("#svg");
    // marker: arrow head
    let marker = svg.marker(13, 13, function (add) {
        add.path('M2,2 L2,11 L10,6 L2,2').fill('blue').stroke({width: 1, color: 'blue'})
    }).attr({"refX": "2", "refY": "6", "orient": "auto"});
    // Set the global configs to synchronous
    $.ajaxSetup({
        async: false
    });
    let version = Date.now();

    function createNode(container, data) {
        // 用一个group把节点边框和文本放在一起，这样可以利用group的transform来定义
        // 整个group里面所有节点的坐标位置
        let translateX = data.group.transform.translateX;
        let translateY = data.group.transform.translateY;
        // let label = container.text(data.label.text);
        let label = container.text('');

        let width = data.group.width == null ? (data.label.text.length * 8 + 10) : parseInt(data.group.width);
        let height = data.group.height == null ? 28 : parseInt(data.group.height);
        let group = container.group()
            .transform({translateX: translateX, translateY: translateY})
            .id(data.group.id)
            .data('leftTopX', translateX)
            .data('leftTopY', translateY)
            .data('width', width)
            .data('height', height)
        ;
        // TODO style 的信息暂时hardcode, 后面会用程序控制动态变化的效果。
        let style = {fill: 'none', stroke: 'blue'};
        let attributes = {...style, ...data.shape.attributes};
        // 动态的调用函数，可以支持绘制各种图形，比如:rect, ellipse
        let rect = container[data.shape.type](width, height).attr(attributes);
        group.add(rect);

        label.attr(data.label.attributes);
        group.add(label);
        return group.draggable(); // 支持拖拽
    }

    function buildPathId(start, end) {
        return `${start}_${end}`;
    }

    function buildPath(start, endList, i, path) {
        let startNode = svg.findOne(`#${start}`);
        let endNode = svg.findOne(`#${endList[i]}`);
        let startX = startNode.data('leftTopX') + startNode.data('width') + path.startOffsetX;
        let startY = startNode.data('leftTopY') + startNode.data('height') + path.startOffsetY;
        let endX = endNode.data('leftTopX') + path.endOffsetX;
        let endY = endNode.data('leftTopY') + path.endOffsetY;
        let data = `M ${startX}, ${startY} ${endX}, ${endY}`;
        if (path.curve != null) {
            data = `M ${startX}, ${startY} ${path.curve} ${endX}, ${endY}`;
        }
        return {startX, startY, endX, endY, data};
    }

    function createPath(container, path, marker) {
        let start = path.start;
        let endList = path.end;
        for (let i = 0; i < endList.length; i++) {
            let {startX, startY, endX, endY, data} = buildPath(start, endList, i, path);
            let id = buildPathId(start, endList[i]);
            container.path(data).id(id)
                .fill('none')
                .stroke({width: 1, color: 'blue'})
                .marker('end', marker)
                .data('points', [startX, startY, endX, endY])
                .data('d', data);
        }
    }

    function animate(pathId, loop, callback) {
        let d = $(`#${pathId}`).data('d');
        let move = snap.path(d).attr({fill: "none", stroke: "red", opacity: "1"});
        let points = $(`#${pathId}`).data('points');
        let circle = snap.circle(points[0], points[1], 10);
        circle.attr({
            fill: 'green',
            id: 'circle-' + pathId,
            stroke: "green",
            strokeWidth: 2
        });
        circle.drawAtPath(move, 1000, {callback: callback});
    }

    // a matrix is a group of nodes replicated from data
    function createMatrix(container, data) {
        let group = container.group();
        let matrix = data.shape.matrix;
        if (matrix != null) {
            for (let i = 0; i < matrix.row.count; i++) {
                let row = group.group().id(data.group.id.replace("{row}", i).replace("{column}", 'x'));
                for (let j = 0; j < matrix.column.count; j++) {
                    let nodeReplica = JSON.parse(JSON.stringify(data));
                    nodeReplica.group.transform.translateX += nodeReplica.group.width * j + nodeReplica.shape.matrix.column.offset;
                    nodeReplica.group.transform.translateY += nodeReplica.shape.matrix.row.offset * i;
                    nodeReplica.label.text = nodeReplica.label.text.replace("{}", j);
                    nodeReplica.group.id = nodeReplica.group.id.replace("{row}", i).replace("{column}", j);
                    createNode(row, nodeReplica);
                }
            }
        }
    }

    SVG.on(document, 'DOMContentLoaded', function () {
        $.getJSON("data/producer/producer.json?" + version, function (data) {
            data.forEach(node => createNode(svg, node))
        });

        $.getJSON("data/producer/buffer.json?" + version, function (node) {
            createMatrix(svg, node);
        });
        for (let i = 0; i < 3; i++) {
            $.getJSON(`data/producer/cluster-broker${i}.json?${version}`, function (node) {
                createMatrix(svg, node);
            });
            $.getJSON("data/producer/cluster.json?" + version, function (node) {
                createMatrix(svg, node);
            });
        }
        $.getJSON("data/producer/paths.json?" + version, function (data) {
            data.forEach(value => createPath(svg, value, marker));
        });

        function append(index, cb) {
            if (index > 5) {
                return cb();
            }
            console.log(`producer appends new message ${index}`);
            animate(`producer_buffer-0-0`, 1, function (callback) {
                snap.select(`#buffer-0-${index} text`).attr({text: `${index}`, x: 16, y: 28});
                if (cb != null) callback();
            }.bind(null, append.bind(null, index + 1, cb)));
        }

        let send = function (replicate) {
            console.log('buffer is full. send to broker...');
            animate(`buffer-0-5_broker1-0-0`, 1, function () {
                for (let i = 5; i > -1; i--) {
                    snap.select(`#buffer-0-${i} text`).attr({text: ``, x: 16, y: 28});
                    snap.select(`#broker1-0-${i} text`).attr({text: `${i}`, x: 16, y: 28});
                }
                replicate();
            });
        };

        let replicate = function () {
            setTimeout(function () {
                console.log('leader got new message. replicate to follower0...');
                animate(`broker1-0-5_broker0-0-5`, 1, function () {
                    for (let i = 5; i > -1; i--) {
                        snap.select(`#broker0-0-${i} text`).attr({text: `${i}`, x: 16, y: 28});
                    }
                });
            }, 500);
            setTimeout(function () {
                console.log('leader got new message. replicate to follower1...');
                animate(`broker1-0-5_broker2-0-5`, 1, function () {
                    for (let i = 5; i > -1; i--) {
                        snap.select(`#broker2-0-${i} text`).attr({text: `${i}`, x: 16, y: 28});
                    }
                });
            }, 1000);


        };
        setTimeout(function () {
            for (let i = 5; i > -1; i--) {
                snap.select(`#broker0-0-${i} rect`).attr({text: `${i}`, x: 10, y: 10, "stroke-dasharray": "5,5"});
                snap.select(`#broker0-2-${i} rect`).attr({text: `${i}`, x: 10, y: 10, "stroke-dasharray": "5,5"});
                snap.select(`#broker1-1-${i} rect`).attr({text: `${i}`, x: 10, y: 10, "stroke-dasharray": "5,5"});
                snap.select(`#broker1-2-${i} rect`).attr({text: `${i}`, x: 10, y: 10, "stroke-dasharray": "5,5"});
                snap.select(`#broker2-0-${i} rect`).attr({text: `${i}`, x: 10, y: 10, "stroke-dasharray": "5,5"});
                snap.select(`#broker2-1-${i} rect`).attr({text: `${i}`, x: 10, y: 10, "stroke-dasharray": "5,5"});
            }
            snap.select(`#producer text`).attr({text: `producer`, x: 16, y: 28});
        }, 1000);

        function play(){
            append(0, send.bind(null, replicate));
        }

        $("#show_all").click(function () {
            snap.selectAll('text').attr({text: ``, x: 16, y: 28});
            snap.select(`#producer text`).attr({text: `producer`, x: 16, y: 28});
            play();
        });
    });
</script>
<input id="show_all" type="button" class="button" value="Show all" style="float: right;">
</body>
</html>